﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using Azure.TableQuery.Core;
using System.CodeDom.Compiler;
using System.IO;
using Azure.TableQuery.Core.Interfaces;

namespace Azure.TableQuery.WebClient
{
    public partial class Default : System.Web.UI.Page
    {
        CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("TableStorage");
        
        protected List<string> TableNames
        {
            get
            {
                if (this.ViewState["tables"] == null)
                {
                    var tableClient = account.CreateCloudTableClient();
                    this.TableNames = tableClient.ListTables().ToList();
                }
                return this.ViewState["tables"] as List<string>;
            }
            set
            {
                this.ViewState["tables"] = value;
            }
        }

        protected void treeTables_TreeNodePopulate(object sender, TreeNodeEventArgs e)
        {
            string baseAddress = account.CreateCloudTableClient().BaseUri.ToString();
            if (e.Node.Text == "Tables")
            {
                var tableClient = account.CreateCloudTableClient();
                this.TableNames = tableClient.ListTables().ToList();
                foreach (var table in TableNames)
                {
                    TreeNode node = new TreeNode();
                    node.Text = table;
                    node.ImageUrl = "~/images/table.png";
                    node.PopulateOnDemand = true;
                    
                    e.Node.ChildNodes.Add(node);
                }
            }
            else if (e.Node.Depth == 1)
            {
                string tableName = e.Node.Text;
                if (TableNames.Contains(tableName))
                {
                    var context = new GenericTableContext(baseAddress, account.Credentials);
                    var entity = context.GetFirstOrDefault(tableName);

                    if (entity != null)
                    {
                        e.Node.PopulateOnDemand = false;

                        foreach (var key in entity.GetProperties().Keys)
                        {
                            e.Node.ChildNodes.Add(
                                CreateNode(key + " (" + GetCSharpTypeName(entity.GetProperties()[key]) + ")"));
                        }
                    }
                }
            }    
        }

        private string GetCSharpTypeName(Type type)
        {
            switch (type.FullName)
            {
                case "System.String": return "string";
                case "System.Byte": return "byte";
                case "System.SByte": return "sbyte";
                case "System.Int16": return "short";
                case "System.Int32": return "int";
                case "System.Int64": return "long";
                case "System.Double": return "double";
                case "System.Single": return "float";
                case "System.Boolean": return "bool";
                case "System.Decimal": return "decimal";
                case "System.DateTime": return "DateTime";
                case "System.Binary": return "byte[]";
                case "System.Guid": return "Guid";

                default: throw new NotSupportedException("Not supported type " + type.FullName);
            }   
        }

        private TreeNode CreateNode(string nodeText)
        {
            TreeNode node = new TreeNode();
            node.Text = nodeText;
            node.PopulateOnDemand = false;

            bool isKey = false;
            if (nodeText.IndexOf("PartitionKey") > -1 
                || nodeText.IndexOf("RowKey") > -1)
            {
                isKey = true;
            }

            if (isKey)
            {
                node.ImageUrl = "~/images/primarykey.png"; 
            }
            else
            {
                node.ImageUrl = "~/images/column.png";
            }
            return node;
        }

        protected void treeTables_SelectedNodeChanged(object sender, EventArgs e)
        {
            if (treeTables.SelectedNode != null)
            {
                string selectedNodeText = treeTables.SelectedNode.Text;
                if (TableNames.Contains(selectedNodeText))
                {
                    string queryForamt = "from {0} in {1}\nselect {0}";
                    txtQueryText.Text = string.Format(queryForamt, selectedNodeText.Substring(0, 1).ToLower(),
                        selectedNodeText);

                    HidePanels();
                }
            }
        }
        private void ResetError()
        {
            txtErrors.Text = string.Empty;
        }

        private void ShowError()
        {
            pnlErrors.Visible = true;
            pnlGrid.Visible = false;
        }
        private void ShowGrid()
        {
            pnlErrors.Visible = false;
            pnlGrid.Visible = true;
        }
        private void HidePanels()
        {
            pnlErrors.Visible = false;
            pnlGrid.Visible = false;
        }

        protected void btnRun_Click(object sender, ImageClickEventArgs e)
        {
            if (treeTables.SelectedNode == null
                || !TableNames.Contains(treeTables.SelectedNode.Text))
            {
                ResetError();
                WriteError("No table selected");
                ShowError();
                return;
            }


            string baseAddress = account.CreateCloudTableClient().BaseUri.ToString();
            string tableName = treeTables.SelectedNode.Text;

            var context = new GenericTableContext(baseAddress, account.Credentials);
            var entity = context.GetFirstOrDefault(tableName);

            if (entity == null)
            {
                ResetError();
                WriteError("The selected table is empty.  Schema cannot be determined.");
                ShowError();
                return;
            }

            string queryText = txtQueryText.Text.Trim().Replace(Environment.NewLine, " ");

            var compiler = new QueryCompiler(entity, queryText);
            var results = compiler.Compile();
            
            if (!results.Errors.HasErrors)
            {
                var assemblyPath = compiler.GetOutputFileName();
                var instance = QueryActivator.CreateInstance(assemblyPath);

                var query = instance.CreateQueryContext(baseAddress, account.Credentials);

                switch (ddlOutputType.SelectedIndex)
                {
                    case 0:
                        OutputToGrid(query);
                        ShowGrid();
                        break;

                    case 1:
                        OutputToXml(query, tableName);
                        break;

                    case 2:
                        OutputToCompressedXml(query, tableName);
                        break;
                }
                
            }
            else
            {
                ResetError();
                foreach (CompilerError error in results.Errors)
                {
                    WriteError(error.ErrorText);
                }
                ShowError();
            }
        }

        

        private void OutputToGrid(IAzureTableQuery query)
        {
            gridResults.AutoGenerateColumns = true;
            query.OutputToGrid(gridResults);
        }

        private void OutputToXml(IAzureTableQuery query, string tableName)
        {
            Response.Clear();
            Response.ContentType = "text/xml";
            Response.AppendHeader("Content-Disposition", "attachment; filename=" + tableName + "Query.xml");

            OutputQueryResults(query, false);
        }

        private void OutputToCompressedXml(IAzureTableQuery query, string tableName)
        {
            Response.Clear();
            Response.ContentType = "application/zip";
            Response.AppendHeader("Content-Disposition", "attachment; filename=" + tableName + "Query.zip");
            OutputQueryResults(query, true);
        }

        private void OutputQueryResults(IAzureTableQuery query, bool compress)
        {
            using (MemoryStream file = query.OutputToXml(compress))
            {
                int length = (int)file.Length;

                byte[] bytes = new byte[length];
                Array.Copy(file.GetBuffer(), bytes, length);

                Response.BinaryWrite(bytes);
            }
            Response.End();
        }


        private void WriteError(string message)
        {
            txtErrors.Text += message + Environment.NewLine;
        }
    }
}
